package com.sojson.config.datasource;

import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.sql.DataSource;

import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties;
import com.alibaba.druid.util.Utils;
import com.baomidou.mybatisplus.extension.incrementer.OracleKeyGenerator;
import com.github.pagehelper.PageInterceptor;
import com.sojson.config.datasource.bean.impl.DbDruidPro;
import com.sojson.config.datasource.enums.EDataSourceType;
import com.sojson.constant.Constant;
import com.sojson.util.ArrayUtil;
import com.sojson.util.StringUtil;
import com.sojson.util.properties.PropertiesUtil;
import com.sojson.util.text.Convert;

import lombok.extern.slf4j.Slf4j;

/**
 * 配置多数据源
 * 
 * @author liu
 * @date 2021-07-04
 */
@Slf4j
@Configuration
public class DruidConfig {

    /** 所有类型的数据源集合 */
    private static Map<String, Map<Object, Object>> dataSource;
    /** 所有类型的数据源的默认数据源集合 */
    private static Map<String, String> dataSourceDefaultNames = new HashMap<>();
    /** Mybatis配置文件的位置 */
    private static final String CONFIG_LOCATION = Constant.PROPERTIES.getProperty("so.mybatis.config.location");
    /** Bean文件的位置(逗号拼接,可用模糊匹配) */
    private static final String MAPPER_ENTITY_LOCATIONS =
        Constant.PROPERTIES.getProperty("so.mybatis.mapper.entity.locations");
    /** 接口文件的位置(逗号拼接,可用模糊匹配) */
    private static final String MAPPER_INTERFACE_LOCATIONS =
        Constant.PROPERTIES.getProperty("so.mybatis.mapper.interface.locations");
    /** 接口映射文件的位置(逗号拼接,可用模糊匹配) */
    private static final String MAPPER_XML_LOCATIONS =
        Constant.PROPERTIES.getProperty("so.mybatis.mapper.xml.locations");
    /** 数据源属性Key的前缀 */
    private static final String PREFIX_DATA_SOURCE = "so.jdbc.dataSource.dataSource.";
    /** 数据库名 */
    private static final String DATABASE_NAME = Constant.PROPERTIES.getProperty("so.jdbc.databaseName");
    /** SSL信息 */
    private static final String SSL = Constant.PROPERTIES.getProperty("so.jdbc.SSL");
    /** master数据源的Key */
    private static final String MASTER = "master";
    /** 默认SqlSessionFactory的Key */
    private static String sqlSessionFactoryDefaultKey;
    /** 默认数据源的Key */
    private static String dataSourceDefaultKey;
    /** 数据源开关/默认关闭,的后缀 */
    private static final String FIELD_ENABLED = ".enabled";
    /** 数据库-协议的后缀 */
    private static final String FIELD_URL = ".url";
    /** 数据库-帐号的后缀 */
    private static final String FIELD_USERNAME = ".username";
    /** 数据库-密码的后缀 */
    private static final String FIELD_PASSWORD = ".password";
    /** 是否是默认数据源(不设置为false)的后缀 */
    private static final String FIELD_IS_DEFAULT = ".isDefault";

    static {
        // 获取所有类型的数据源集合
        try {
            dataSource = getDataSources();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * 去除监控页面底部的广告
     */
    @SuppressWarnings({"rawtypes", "unchecked"})
    @Bean
    @ConditionalOnProperty(name = "spring.datasource.druid.statViewServlet.enabled", havingValue = "true")
    public FilterRegistrationBean removeDruidFilterRegistrationBean(DruidStatProperties properties) {
        // 获取web监控页面的参数
        DruidStatProperties.StatViewServlet config = properties.getStatViewServlet();
        // 提取common.js的配置路径
        String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*";
        String commonJsPattern = pattern.replaceAll("\\*", "js/common.js");
        final String filePath = "support/http/resources/js/common.js";
        // 创建filter进行过滤
        Filter filter = new Filter() {
            @Override
            public void init(javax.servlet.FilterConfig filterConfig) throws ServletException {}

            @Override
            public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
                throws IOException, ServletException {
                chain.doFilter(request, response);
                // 重置缓冲区，响应头不会被重置
                response.resetBuffer();
                // 获取common.js
                String text = Utils.readFromResource(filePath);
                // 正则替换banner, 除去底部的广告信息
                text = text.replaceAll("<a.*?banner\"></a><br/>", "");
                text = text.replaceAll("powered.*?shrek.wang</a>", "");
                response.getWriter().write(text);
            }

            @Override
            public void destroy() {}
        };
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        registrationBean.setFilter(filter);
        registrationBean.addUrlPatterns(commonJsPattern);
        return registrationBean;
    }

    // @Bean
    public MapperScannerConfigurer mapperScannerConfigurer() {
        MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
        // 配置接口文件位置
        mapperScannerConfigurer.setBasePackage(MAPPER_INTERFACE_LOCATIONS);
        // 当我们配置了多个SqlSessionFactoryBean的时候就需要指定一个
        // 这里不能使用sqlSessionFactory或sqlSessionTemplate的对象来注册
        // 因为MapperScannerConfigurer将会创建MapperFactoryBean之后自动装配
        // 但是如果你使用了一个以上的DataSource,则自动装配可能会失效,所以通过类名称注册
        mapperScannerConfigurer.setSqlSessionTemplateBeanName("customSqlSessionTemplate");
        // mapperScannerConfigurer.setMarkerInterface(getClass());
        return mapperScannerConfigurer;
    }

    /**
     * 创建SqlSessionTemplate
     * 
     * @return
     * @throws Exception
     */
    // @Bean
    public CustomSqlSessionTemplate customSqlSessionTemplate() throws Exception {
        // 获取SqlSessionFactory集合
        Map<String, SqlSessionFactory> sqlSessionFactorys = getSqlSessionFactorys();

        // 创建SqlSessionTemplate并设置默认SqlSessionFactory
        CustomSqlSessionTemplate customSqlSessionTemplate =
            new CustomSqlSessionTemplate(sqlSessionFactorys.get(sqlSessionFactoryDefaultKey));
        // 设置所有SqlSessionFactory
        customSqlSessionTemplate.setSqlSessionFactorys(sqlSessionFactorys);

        // 返回SqlSessionTemplate
        return customSqlSessionTemplate;
    }

    /**
     * 创建默认数据源</BR>
     * 一些依赖需要有数据源(他们有数据源的自动配置,找不到就报错),比如Druid的监控
     * 
     * @return
     * @throws SQLException
     */
    @Bean
    public DataSource dataSource() throws SQLException {
        // 初始化一个数据源集合
        Map<Object, Object> dss = new HashMap<>();
        // 数据源集合的名称集合
        Map<Object, List<Object>> hashMap = new HashMap<>();

        // 获取所有类型的数据源集合并合并成一个集合
        Set<Entry<String, Map<Object, Object>>> entrySet = dataSource.entrySet();
        for (Entry<String, Map<Object, Object>> entry : entrySet) {
            dss.putAll(entry.getValue());
            hashMap.put(entry.getKey(), new ArrayList<>(entry.getValue().keySet()));
        }
        // 获取数据源
        return getCustomSqlSessionFactoryDataSource(dataSourceDefaultKey, dss, hashMap);
    }

    /**
     * 获取SqlSessionFactory集合
     * 
     * @return
     * @throws Exception
     */
    public Map<String, SqlSessionFactory> getSqlSessionFactorys() throws Exception {
        // 初始化一个集合
        Map<String, SqlSessionFactory> sqlSessionFactorys = new HashMap<>();

        // 获取SqlSessionFactoryBean集合
        Map<String, SqlSessionFactoryBean> sqlSessionFactoryBeans = getSqlSessionFactoryBeans();
        // 创建SqlSessionFactory并添加到集合中
        Set<Entry<String, SqlSessionFactoryBean>> entrySet = sqlSessionFactoryBeans.entrySet();
        for (Entry<String, SqlSessionFactoryBean> entry : entrySet) {
            // 将SqlSessionFactory添加到集合中
            sqlSessionFactorys.put(entry.getKey(), entry.getValue().getObject());
        }
        // 返回SqlSessionFactory集合
        return sqlSessionFactorys;
    }

    /**
     * 获取SqlSessionFactoryBean集合
     * 
     * @return
     * @throws Exception
     */
    public Map<String, SqlSessionFactoryBean> getSqlSessionFactoryBeans() throws Exception {
        // 初始化一个集合
        Map<String, SqlSessionFactoryBean> factorys = new HashMap<>();

        // 获取所有类型的数据源
        Map<String, Map<Object, Object>> dataSources = getDataSources();

        log.info("设置默认的SqlSessionFactory数据源池集合:[");
        // 将每种类型的数据源添加到对应的SqlSessionFactoryBean并初始化SqlSessionFactoryBean
        Set<Entry<String, Map<Object, Object>>> entrySet = dataSources.entrySet();
        for (Entry<String, Map<Object, Object>> entry : entrySet) {
            // 获取数据源的类型Key
            String key = entry.getKey();
            // 获取数据源的类型集合
            Map<Object, Object> value = entry.getValue();
            // 获取SqlSessionFactory的默认数据源Key
            String dataSourceDefaultName = dataSourceDefaultNames.get(key);
            // 创建SqlSessionFactoryBean
            SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();

            // 配置数据源
            factoryBean.setDataSource(getCustomRoutingDataSource(key, dataSourceDefaultName, value));
            // 配置Mybatis配置文件的位置
            factoryBean.setConfigLocation(PropertiesUtil.getResource(CONFIG_LOCATION));
            // 配置Bean文件的位置
            factoryBean.setTypeAliasesPackage(MAPPER_ENTITY_LOCATIONS);
            // 配置接口映射文件的位置
            factoryBean.setMapperLocations(PropertiesUtil.getResourceArr(MAPPER_XML_LOCATIONS));
            // 设置分页插件
            factoryBean.setPlugins(new Interceptor[] {pageInterceptor()});
            // 添加到集合中
            factorys.put(key, factoryBean);

            // 输出日志
            log.info("    " + key + "的数据源池:[");
            Set<Object> keySet = value.keySet();
            int size = keySet.size();
            int i = 0;
            for (Object object : keySet) {
                if (i == size - 1) {
                    log.info("        " + object.toString());
                } else {
                    log.info("        " + object + ",");
                }
                i++;
            }
            log.info("    ] " + key + "的默认数据源为: {}", dataSourceDefaultName);
        }
        log.info("]");
        log.info("默认的SqlSessionFactory为: " + sqlSessionFactoryDefaultKey);
        log.info("默认的SqlSessionFactory所有数据源已初始化!");
        return factorys;
    }

    /**
     * 注入分页插件
     * 
     * @return
     */
    // @Bean
    public Interceptor pageInterceptor() {
        // 设置分页插件
        // pagehelper-spring-boot-starter的分页拦截器
        PageInterceptor interceptor = new PageInterceptor();
        // pagehelper的分页拦截器
        // PageHelper interceptor = new PageHelper();

        Properties props = new Properties();
        // 分页属性的前缀
        String keyPrefix = "pagehelper.";
        // 分页属性的前缀的长度
        int len = keyPrefix.length();
        // 获取所有分页属性并设置到分页插件中
        Set<Entry<Object, Object>> entrySet = Constant.PROPERTIES.entrySet();
        for (Entry<Object, Object> entry : entrySet) {
            String key = entry.getKey().toString();
            if (key.startsWith(keyPrefix)) {
                props.put(key.substring(len), entry.getValue());
            }
        }
        // 设置属性
        interceptor.setProperties(props);
        return interceptor;
    }

    // @Bean
    public OracleKeyGenerator oracleKeyGenerator() {
        return new OracleKeyGenerator();
    }

    /**
     * 获取自定义数据源
     * 
     * @param sqlSessionFactoryName SqlSessionFactory类型Key
     * @param defaultDataSourceName 默认数据源名称
     * @param dataSources           数据源池
     * @return
     * @throws SQLException
     */
    public CustomRoutingDataSource getCustomRoutingDataSource(String sqlSessionFactoryName,
        String defaultDataSourceName, Map<Object, Object> dataSources) throws SQLException {
        return new CustomRoutingDataSource(sqlSessionFactoryName, defaultDataSourceName, dataSources);
    }

    /**
     * 获取自定义数据源
     * 
     * @param defaultDataSourceName 默认数据源名称
     * @param dataSources           数据源池
     * @param dataSourceNames       数据源名称集合
     * @return
     * @throws SQLException
     */
    public CustomSqlSessionFactoryDataSource getCustomSqlSessionFactoryDataSource(String defaultDataSourceName,
        Map<Object, Object> dataSources, Map<Object, List<Object>> dataSourceNames) throws SQLException {
        return new CustomSqlSessionFactoryDataSource(defaultDataSourceName, dataSources, dataSourceNames);
    }

    /**
     * 获取所有类型的数据源集合
     * 
     * @return
     * @throws SQLException
     */
    public static Map<String, Map<Object, Object>> getDataSources() throws SQLException {
        // 初始化一个集合
        Map<String, Map<Object, Object>> dataSourceFactorys = new HashMap<>();

        // 获取所有类型的数据源配置信息
        Map<String, List<DbDruidPro>> db = getDb();

        // 遍历所有配置信息并创建数据源
        Set<Entry<String, List<DbDruidPro>>> entrySet = db.entrySet();
        for (Entry<String, List<DbDruidPro>> entry : entrySet) {
            // 初始化单个类型的数据源集合
            Map<Object, Object> dataSources = new HashMap<>();
            // 获取这个类型的数据源Key
            String key = entry.getKey();
            // 获取这个类型的数据源配置集合
            List<DbDruidPro> value = entry.getValue();
            // 创建数据源配添加到集合中
            for (int i = 0; i < value.size(); i++) {
                // 构建数据源
                DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
                // 获取数据源配置信息
                DbDruidPro dbDruidPro = value.get(i);
                // 初始化数据源
                dbDruidPro.init(dataSource);
                // 获取数据源的Key
                String dataSourceKey = dbDruidPro.getKey();
                // 将数据源添加到数据源集合中
                dataSources.put(dataSourceKey, dataSource);

                // 如果是默认数据源就将数据源名称添加到默认数据源名称集合中
                if (dbDruidPro.getIsDefault()) {
                    if (StringUtil.isBlankObject(dataSourceDefaultNames.get(key))) {
                        dataSourceDefaultNames.put(key, dataSourceKey);
                    }
                }
            }
            // 将单个类型的数据源集合添加到总集合中
            dataSourceFactorys.put(key, dataSources);
            // 如果这个类型的数据源集合没有默认数据源就随机选一个
            if (StringUtil.isBlankObject(dataSourceDefaultNames.get(key))) {
                dataSourceDefaultNames.put(key, ArrayUtil.getRandomKey(dataSources).toString());
            }
        }
        // 返回所有类型的数据源集合
        return dataSourceFactorys;
    }

    /**
     * 获取所有类型的数据源的配置信息
     * 
     * @return
     */
    public static Map<String, List<DbDruidPro>> getDb() {
        // 初始化一个集合
        Map<String, List<DbDruidPro>> hashMap = new HashMap<>(2);
        Properties properties = Constant.PROPERTIES;
        // 遍历所有属性信息
        Set<Entry<Object, Object>> entrySet = properties.entrySet();

        String mapKey = null;
        for (Entry<Object, Object> entry : entrySet) {
            // so.jdbc.dataSource.dataSource.master-1.enabled
            String key = entry.getKey().toString();
            // 如果开头不是数据源的Key就跳过
            if (!key.startsWith(PREFIX_DATA_SOURCE)) {
                continue;
            }
            // so.jdbc.dataSource.dataSource.master-1
            String prefix = key.substring(0, key.lastIndexOf("."));
            // master-1.enabled
            String suffix = key.substring(PREFIX_DATA_SOURCE.length());
            // master-1
            String dataSourceMapKey = suffix.substring(0, suffix.lastIndexOf("."));
            // master
            mapKey = suffix.substring(0, suffix.lastIndexOf("-"));
            // 如果key不是*.enabled就结束
            if (!key.equals(prefix + FIELD_ENABLED)) {
                continue;
            }
            // 设置数据源的配置信息
            DbDruidPro db = new DbDruidPro();
            db.setEnabled(Convert.toBool(properties.getProperty(prefix + FIELD_ENABLED), false));
            db.setKey(dataSourceMapKey);
            String url = properties.getProperty(prefix + FIELD_URL);
            db.setUrl(StringUtil.isNotBlankObject(url) ? url + "/" + DATABASE_NAME + SSL : null);
            db.setUsername(Convert.toStr(properties.getProperty(prefix + FIELD_USERNAME), "root"));
            db.setPassword(Convert.toStr(properties.getProperty(prefix + FIELD_PASSWORD), "123456"));

            // 数据源是否开启并且数据源配置信息不为空
            if (db.getEnabled()
                && StringUtil.isNotBlankObjectListAll(db.getUrl(), db.getUsername(), db.getPassword())) {
                // 是否是默认数据源,如果是就标记默认数据源的Key
                if (Convert.toBool(properties.getProperty(prefix + FIELD_IS_DEFAULT), false)) {
                    sqlSessionFactoryDefaultKey = mapKey;
                    dataSourceDefaultKey = dataSourceMapKey;
                    db.setIsDefault(true);
                } else {
                    db.setIsDefault(false);
                }
                // 如果这个类型的数据源还没创建过就跳过
                List<DbDruidPro> list = hashMap.get(mapKey);
                if (StringUtil.isBlankObject(list)) {
                    list = new ArrayList<>(10);
                    hashMap.put(mapKey, list);
                }
                // 将数据源添加到集合中
                list.add(db);
            }
        }

        // 默认数据源为空
        if (sqlSessionFactoryDefaultKey == null) {
            // 获取指定默认数据源
            List<DbDruidPro> list = hashMap.get(MASTER);
            // 默认数据源如果还是空就随便设置一个
            if (list != null) {
                sqlSessionFactoryDefaultKey = MASTER;
            } else {
                sqlSessionFactoryDefaultKey = mapKey;
                list = hashMap.get(mapKey);
            }
            dataSourceDefaultKey = ArrayUtil.getRandom(list).getKey();
        }
        return hashMap;
    }

    /**
     * 获取数据源在集合中的Key
     * 
     * @param dataSourceType
     * @param index
     * @return
     */
    public static String getPrefixKey(EDataSourceType dataSourceType, int index) {
        return getPrefixKey(dataSourceType.name(), index);
    }

    /**
     * 获取数据源在集合中的Key
     * 
     * @param dataSourceType
     * @param index
     * @return
     */
    public static String getPrefixKey(String dataSourceType, int index) {
        return dataSourceType + index;
    }

    /**
     * 获取数据源在集合中的Key
     * 
     * @param dataSourceType
     * @param index
     * @return
     */
    public static String getPrefixKey(EDataSourceType dataSourceType, String index) {
        return getPrefixKey(dataSourceType.name(), index);
    }

    /**
     * 获取数据源在集合中的Key
     * 
     * @param dataSourceType
     * @param index
     * @return
     */
    public static String getPrefixKey(String dataSourceType, String index) {
        return dataSourceType + index;
    }

}